LOADING...

加载过慢请开启缓存(浏览器默认开启)

loading

Games202 Lecture3 (Shadow 1)

2022/3/11

在做opengl的时候做到了shadowmapping,效果做出来了但其实并不能算吃的很透

刚巧想起之前在202已结搭建好了框架环境,可以成功把202娘跑起来

所以会跟着202好好的把课听一遍

这次开坑的任务:

总结202课上精要内容,加入自己理解作为输出

在作业1的框架上完成shadowmapping的硬阴影

开始吧 Let ‘s go!


Recap Shadow Mapping

做两次光栅化(pass)

第一次从光的视角(light space)做一次pass,得到光看到的更近的更浅的深度值

将其渲染到一个texture上记录下来,作为ShadowMap

image-20220311104355661

第二次从观察视角(camera)做一次pass,得到物体更远的深度值

取一阶段的ShadowMap深度进行比较

image-20220311104321858

若第二次的深度值小于第一次的深度值,则生成硬阴影


优势:不需要场景的几何信息,一旦shadowmap已经生成,shadowmap就能作为场景的几何表示

缺陷:会发生自遮挡和比较严重的走样(锯齿)


备注:生成z值时用的矩阵如果是用正交的,第二个阶段也要拿正交的去比

同理用透视生成的z也一样,两者只要对应上都能生成正确结果

但大佬提醒,平行光用正交投影,点光源和聚光灯用透视。


self-occlusion

如果没有在做深度对比的时候进行bias(偏移)出来的图大概率是像这样的:

(QQ图片20220311110853.jpg

原因是这样的:

我们在第一阶段用一个像素来记录深度值,但此时如果光对于平面来说是斜照的

image-20220311111336669

那一块区域就都只能取小红线这个深度值(即小红线就是像素记录的深度值)

当从上往下垂直照射(光线和平面法线重合)的时候,自遮挡的现象最不明显

为了尽量缩小影响bias带来的计算偏差

因此我们会根据光照和法线的夹角来计算一个bias在比较深度的时候使用:

float bias = max(0.05 * (1.0 - dot(fs_in.Normal, lightDir)), 0.005);

这样做也会带来一些问题,例如我们看上去物体就像浮在表面上一样

阴影并不是贴合着物体的轮廓的,我们称这种现象为悬浮(Peter Panning)

(这个概念在Learnopengl Shadowmapping有不错的解释:[阴影映射 - LearnOpenGL CN (learnopengl-cn.github.io)](https://learnopengl-cn.github.io/05 Advanced Lighting/03 Shadows/01 Shadow Mapping/#_6))


RTR doesn’t trust in complexity(实时渲染不相信复杂度)就像电子竞技不相信眼泪哈哈哈


晚上玩了下原神观察了下阴影,听大佬说人物的阴影用的CSM(Cascaded Shadow Mapping 级联阴影映射)

即给不同位置的shadowmap不同的分辨率,降低开销同时也能提升细节

动态阴影基本上都是用各种改进的Shadowmap做的,还有就是光追阴影

因此Shadowmap需要解决改进的就是锯齿问题


The Math Behind Shadow Map

image-20220311230849772

将两个函数乘积再积分近似等于积分再乘积(分母相当于做归一化)

当g(x)的support(支撑集)很小,或者g(x)的函数曲线足够光滑(smooth)


image-20220312000423444

image-20220312001718439

左边是visibility,右边是纯Shader的结果

对于点光源和方向光源来说,相当于是Small Support满足了第一条件

所以是可以近似认为,可以用约等式将shader和visibility的部分拆开分别计算再叠加

对于面光源来说,面光源内的radiance认为是相等各处不变的,即L是smooth的,也可以满足第一条件

而对于shading point来说diffuse的表面我们认为变化是比较小的

而glossy的表面(被打磨的铜镜之类的材质效果,是较为模糊的镜面)是较为剧变的,结果就比较不准确


Percentage Closer Soft Shadow (PCSS)

对于硬阴影来说,阴影在边界上没有从无到有的过渡,看上去很不自然

在生活中,光源基本上都是理想的面光源,看到的阴影都更倾向于软阴影,有一个良好的过渡。

PCSS是用来生成软阴影的技术,建立在PCF(percentage Closer Filter)之上

PCF是起初用于解决阴影边界的抗锯齿而非计算软阴影。

PCF不是对最后生成的结果的锯齿做模糊,而是在做阴影的判断时做的Filter

同样是因为不能先得到一个走样的结果,然后再做一个模糊(和做反走样的概念是一样的)

PCF同样不是对生成的ShadowMap做了模糊,因为对Shadowmap和z值比较的结果也就是我们最后的

shadow值是一个非零即一的结果,即使对shadowmap做比较最后的shadow值依然是非零即一的,

这没有意义,无法达到抗锯齿的效果

PCF的做法是在投影到light space找对应像素时,不单单只找那一个像素,而是找周围一圈的像素做一个平均(Filter)

![image-20220312005309957]image-20220312005309957.png)


借用learnOpenGL的对此的解释:(个人感觉很到位而且给出了具体的代码演示)

核心思想是从深度贴图中多次采样,每一次采样的纹理坐标都稍有不同。

每个独立的样本可能在也可能不再阴影中。所有的次生结果接着结合在一起,进行平均化,我们就得到了柔和阴影。

float shadow = 0.0;
vec2 texelSize = 1.0 / textureSize(shadowMap, 0);
for(int x = -1; x <= 1; ++x)
{
    for(int y = -1; y <= 1; ++y)
    {
        float pcfDepth = texture(shadowMap, projCoords.xy + vec2(x, y) * texelSize).r; 
        shadow += currentDepth - bias > pcfDepth ? 1.0 : 0.0;        
    }    
}
shadow /= 9.0;

这个textureSize返回一个给定采样器纹理的0级mipmap的vec2类型的宽和高。

用1除以它返回一个单独纹理像素的大小,我们用以对纹理坐标进行偏移,确保每个新样本,来自不同的深度值。

这里我们采样得到9个值,它们在投影坐标的x和y值的周围,为阴影阻挡进行测试,并最终通过样本的总数目将结果平均化。

使用更多的样本,更改texelSize变量,就可以增加阴影的柔和程度。

效果可行,但对于一个shading point要做更多次的计算,所以会变得很慢


image-20220312010426704

课程中的对比图

image-20220312005843526

这是自己手搓的3x3的卷积,Filter越大则阴影越软,因此软阴影就是因此计算而来的

而一般来说,离物体越近的地方阴影就越硬越明显,因此就有了Percentage Closer Soft Shadow的概念

即根据阴影与遮挡物的距离给定不同大小的filter size

image-20220312020418634

这张图很好的说明了Light面越大则软阴影的区域就越大(图中相似三角形的关系)

我们可以想象,若遮挡物Blocker的离接收物Receiver越近,相似三角形就会更小

这就很好的解释了为什么有“离物体越近的地方阴影就越硬”的这个现象

image-20220312021607801

恰巧笔者最近也在玩消光,有机会的话去游戏里好好观察一番


打异度之刃2去咯 2022/3/12 2:20